package com.ma.utils.beanutil;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 @desc 用到的pom
 <!-- lombok -->
 <dependency>
 <groupId>org.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <version>1.18.12</version>
 </dependency>
 <!-- jackson-databind 它包含jackson-core和jackson-annotations -->
 <dependency>
 <groupId>com.fasterxml.jackson.core</groupId>
 <artifactId>jackson-databind</artifactId>
 <version>2.11.1</version>
 </dependency>
 @date 2020-07-22 23:40:26
 @author xiaoma
 */
@UtilityClass
@Log4j2
public class BeanUtil {

    private static final Map<Class<?>, Set<Field>> TRIM_FIELDS = new ConcurrentHashMap<>();
    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * @description 将一个bean(源对象)的属性复制给另一个bean(目标对象)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标对象
     * @return void
     *
     * @date 2020/7/22 23:48
     * @author xiaoma
     */
    public <T, E> void convertTo(T source, E target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return;
        }
        BeanUtils.copyProperties(source, target);
    }

    /**
     * @desc 将一个bean(源对象)的属性复制给另一个类(目标类)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标类
     * @return E
     *
     * @date 2020/7/22 23:53
     * @author xiaoma
     */
    public <T, E> E convertTo(T source, Class<E> target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return null;
        }
        E t = null;
        try {
            t = target.newInstance();
            BeanUtils.copyProperties(source, t);
        } catch (InstantiationException | IllegalAccessException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
        }
        return t;
    }

    /**
     * @desc List<对象>转到另一个List<对象>
     * @param source 1
     * @param target 2
     * @return java.util.List<E>
     *
     * @date 2020/7/23 0:04
     * @author xiaoma
     */
    public <T, E> List<E> convertTo(List<T> source, Class<E> target) {
        if (CollectionUtils.isEmpty(source) || null == target) {
            log.error("source can't be empty and target can't be null");
            return new ArrayList<>();
        }
        return source.parallelStream().map(o -> convertTo(o, target)).collect(Collectors.toList());
    }

    /** ******************************************************************** */

    /**
     * @description 将一个bean(源对象)的属性(去除前后空格)复制给另一个bean(目标对象)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标对象
     * @return void
     *
     * @date 2020/7/22 23:48
     * @author xiaoma
     */
    public static <T, E> void convertTrimTo(T source, E target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return;
        }
        convertTo(trimField(source), target);
    }

    /**
     * @desc 将一个bean(源对象)的属性(去除前后空格)复制给另一个类(目标类)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标类
     * @return E
     *
     * @date 2020/7/22 23:53
     * @author xiaoma
     */
    public static <T, E> E convertTrimTo(T source, Class<E> target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return null;
        }
        return trimField(target, convertTo(source, target));
    }

    /**
     * @desc List<对象>转到另一个List<对象> (去除属性前后空格)
     * @param source 1
     * @param target 2
     * @return java.util.List<E>
     *
     * @date 2020/7/23 0:04
     * @author xiaoma
     */
    public <T, E> List<E> convertTrimTo(List<T> source, Class<E> target) {
        if (CollectionUtils.isEmpty(source) || null == target) {
            log.error("source can't be empty and target can't be null");
            return new ArrayList<>();
        }
        return source.parallelStream().map(o -> convertTrimTo(o, target)).collect(Collectors.toList());
    }

    /** ******************************************************************** */

    /**
     * @description 将一个bean(源对象)的属性(忽略值为null和空串的属性)复制给另一个bean(目标对象)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标对象
     * @return void
     *
     * @date 2020/7/22 23:48
     * @author xiaoma
     */
    public static <T, E> void convertNotBlankTo(T source, E target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return;
        }
        String[] nullOrBlankField = getNullOrBlankField(source);
        BeanUtils.copyProperties(source, target, nullOrBlankField);
    }

    /**
     * @desc 将一个bean(源对象)的属性(忽略值为null和空串的属性)复制给另一个类(目标类)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标类
     * @return E
     *
     * @date 2020/7/22 23:53
     * @author xiaoma
     */
    public static <T, E> E convertNotBlankTo(T source, Class<E> target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return null;
        }
        String[] nullOrBlankField = getNullOrBlankField(source);
        E t = null;
        try {
            t = target.newInstance();
            BeanUtils.copyProperties(source, t, nullOrBlankField);
        } catch (InstantiationException | IllegalAccessException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
        }
        return t;
    }

    /**
     * @desc List<对象>转到另一个List<对象> (忽略值为null和空串的属性)
     * @param source 1
     * @param target 2
     * @return java.util.List<E>
     *
     * @date 2020/7/23 0:04
     * @author xiaoma
     */
    public <T, E> List<E> convertNotBlankTo(List<T> source, Class<E> target) {
        if (CollectionUtils.isEmpty(source) || null == target) {
            log.error("source can't be empty and target can't be null");
            return new ArrayList<>();
        }
        return source.parallelStream().map(o -> convertNotBlankTo(o, target)).collect(Collectors.toList());
    }

    /** ******************************************************************** */

    /**
     * @description 将一个bean(源对象)的属性(去除前后空格并忽略值为null和空串的属性)复制给另一个bean(目标对象)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标对象
     * @return void
     *
     * @date 2020/7/22 23:48
     * @author xiaoma
     */
    public static <T, E> void convertTrimAndNotBlankTo(T source, E target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return;
        }
        convertNotBlankTo(trimField(source), target);
    }

    /**
     * @desc 将一个bean(源对象)的属性(去除前后空格并忽略值为null和空串的属性)复制给另一个类(目标类)中类型和名称相同的属性
     * @param source 1 源对象
     * @param target 2 目标类
     * @return E
     *
     * @date 2020/7/22 23:53
     * @author xiaoma
     */
    public static <T, E> E convertTrimAndNotBlankTo(T source, Class<E> target) {
        if (null == source || null == target) {
            log.error("source or target can't be null");
            return null;
        }
        E t = null;
        t = convertNotBlankTo(trimField(source), target);
        return t;
    }

    /**
     * @desc List<对象>转到另一个List<对象> (去除前后空格并忽略值为null和空串的属性)
     * @param source 1
     * @param target 2
     * @return java.util.List<E>
     *
     * @date 2020/7/23 0:04
     * @author xiaoma
     */
    public <T, E> List<E> convertTrimAndNotBlankTo(List<T> source, Class<E> target) {
        if (CollectionUtils.isEmpty(source) || null == target) {
            log.error("source can't be empty and target can't be null");
            return new ArrayList<>();
        }
        return source.parallelStream().map(o -> convertTrimAndNotBlankTo(o, target)).collect(Collectors.toList());
    }

    /** ******************************************************************** */

    /**
     * @desc bean转为map
     * @param source 1
     * @return java.util.Map<java.lang.String, java.lang.Object>
     *
     * @date 2020/7/23 0:13
     * @author xiaoma
     */
    public <T> Map<String, Object> beanToMap(T source) {
        if (null == source) {
            log.error("source can't be null");
            return new HashMap<>(16);
        }
        Map<String, Object> map = new HashMap<>(16);
        if (source != null) {
            BeanMap beanMap = BeanMap.create(source);
            for (Object key : beanMap.keySet()) {
                map.put(key + "", beanMap.get(key));
            }
        }
        return map;
    }

    /**
     * @desc map转为bean
     * @param source 1
     * @param target 2
     * @return T
     *
     * @date 2020/7/23 0:13
     * @author xiaoma
     */
    public <T> void mapToBean(Map<String, Object> source, T target) {
        if (null == source || null == target) {
            log.error("source and target can't be null");
        }
        try {
            BeanMap beanMap = BeanMap.create(target);
            beanMap.putAll(source);
        } catch (ClassCastException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
        }
    }

    /**
     * @desc map转为bean
     * @param source 1
     * @param target 2
     * @return T
     *
     * @date 2020/7/23 0:17
     * @author xiaoma
     */
    public <T> T mapToBean(Map<String, Object> source, Class<T> target) {
        if (null == source || null == target) {
            log.error("source and target can't be null");
            return null;
        }
        T t = null;
        try {
            t = target.newInstance();
            BeanMap beanMap = BeanMap.create(t);
            beanMap.putAll(source);
            return t;
        } catch (InstantiationException | IllegalAccessException | ClassCastException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
            return null;
        }
    }

    /**
     * @desc List<bean>转为List<Map<String,Object>>
     * @param source 1
     * @return java.util.List<java.util.Map < java.lang.String, java.lang.Object>>
     *
     * @date 2020/7/23 0:21
     * @author xiaoma
     */
    public <T> List<Map<String, Object>> listBeanToListMap(List<T> source) {
        if (CollectionUtils.isEmpty(source)) {
            log.error("source be empty");
            return new ArrayList<>();
        }
        List<Map<String, Object>> list = new ArrayList<>();
        if (source != null && source.size() > 0) {
            Map<String, Object> map = null;
            T bean = null;
            for (int i = 0, size = source.size(); i < size; i++) {
                bean = source.get(i);
                map = beanToMap(bean);
                list.add(map);
            }
        }
        return list;
    }

    /**
     * @desc List<Map < String, Object>>转为List<bean>
     * @param source 1
     * @param target 2
     * @return java.util.List<T>
     *
     * @date 2020/7/23 0:26
     * @author xiaoma
     */
    public <T> List<T> listMapToListBean(List<Map<String, Object>> source, Class<T> target) {
        if (CollectionUtils.isEmpty(source) || null == target) {
            log.error("source can't be empty and target can't be null");
            return new ArrayList<>();
        }

        List<T> list = new ArrayList<>();
        try {
            if (source != null && source.size() > 0) {
                Map<String, Object> map = null;
                for (int i = 0, j = source.size(); i < j; i++) {
                    T t = null;
                    map = source.get(i);
                    t = target.newInstance();
                    mapToBean(map, t);
                    list.add(t);
                }
            }
        } catch (InstantiationException | IllegalAccessException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
        }
        return list;
    }

    /**
     * @desc 转为json字符串
     * @param source 1
     * @return java.lang.String
     *
     * @date 2020/7/24 16:24
     * @author xiaoma
     */
    public <T> String toJson(T source) {
        try {
            return objectMapper.writeValueAsString(source);
        } catch (JsonProcessingException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
            return null;
        }
    }

    /**
     * @desc json转对象，支持 对象、对象数组、map、map数组。如果要转list，请自行调用jackson方法，如objectMapper.readValue(source, new TypeReference<List<User>>() {});
     * @param source 1 源json字符串
     * @param target 2 目标结果：User.class、 User[].class、 Map.class、 Map[].class
     * @return T
     *
     * @date 2020/7/24 16:17
     * @author xiaoma
     */
    public <T> T fromJson(String source, Class<T> target) {
        try {
            return objectMapper.readValue(source, target);
        } catch (JsonProcessingException e) {
            StackTraceElement ste = e.getStackTrace()[0];
            log.error(e.getMessage());
            log.error(ste.getClassName() + "." + ste.getMethodName() + "( LineNumber:" + ste.getLineNumber());
            return null;
        }
    }

    private <T> T trimField(T source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                continue;
            }
            if (!(srcValue instanceof String)) {
                continue;
            }
            String str = (String) srcValue;
            src.setPropertyValue(pd.getName(), str.trim());
        }
        return source;
    }

    private <E> E trimField(Class<E> targetClass, final E target) {
        for (final Field stringField : getTrimField(targetClass)) {
            try {
                final String stringValue = (String) stringField.get(target);
                if (stringValue != null) {
                    stringField.set(target, stringValue.trim());
                }
            } catch (IllegalAccessException e) {
                log.warn("trim error :", e);
            }
        }
        return target;
    }

    private Set<Field> getTrimField(Class<?> entityClass) {
        if (Object.class.equals(entityClass)) {
            return Collections.emptySet();
        }
        Set<Field> trimField = TRIM_FIELDS.get(entityClass);
        if (trimField != null) {
            return trimField;
        }
        trimField = new HashSet<>();
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            if (!field.getType().equals(String.class)) {
                continue;
            }
            field.setAccessible(true);
            trimField.add(field);

        }
        TRIM_FIELDS.put(entityClass, trimField);
        return trimField;
    }

    private String[] getNullOrBlankField(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
                continue;
            }
            if (!(srcValue instanceof String)) {
                continue;
            }
            String str = (String) srcValue;
            if (!StringUtils.isEmpty(str)) {
                continue;
            }
            emptyNames.add(pd.getName());
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }
}
